2. Tratamiento de correlaciones, valores missing y outliers¶
Valores missing, outlier y correlaciones¶
En este notebook se realiza el estudio y preprocesamiento de las variables numéricas y categoricas. Se realizarán los siguientes pasos:
- Cambio de tipos de variables
- Separación en train y test
- Análisis de cada variable con gráficos descriptivos
- Para variables numericas: correlaciones de pearnson, estudio de outliers y estudio de valores missing
- Para variables categoricas: relleno de valores missing, estudio de correlaciones con vCramer
# Importación de la biblioteca IPython.display para mostrar imágenes.
from IPython.display import Image
Image(filename="../Practica_Eda_Miguel_Garcia/images/analisis_outlier.png")
Vemos que son los valores outliers y como afectan a los datos
Image(filename="../Practica_Eda_Miguel_Garcia/images/Missing_data.png")
Estas son las diferentes maneras que existen para administrar los valores missing
Importo librerías¶
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import plotly.express as px
from sklearn.impute import KNNImputer
import scipy.stats as ss
import warnings
pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 5000)
warnings.filterwarnings('ignore')
Funciones¶
Buenas prácticas
Voy guardando las funciones que están automatizadas y pienso que me van a servir en otros proyectos en un funciones_auxiliares.py y lo importo:
import funciones_auxiliares as f_aux
Lectura de datos del preprocesado inicial¶
Lectura de los datos y cambio de tipos de variables
import pandas as pd
data = pd.read_csv("../Practica_Eda_Miguel_Garcia/data/df_initial_preprocessing.csv").drop('Unnamed: 0', axis=1)
list_var_cat, other = f_aux.dame_variables_categoricas(dataset=data)
data[list_var_cat] = data[list_var_cat].astype("category")
list_var_continuous = list(data.select_dtypes('float').columns)
data[list_var_continuous] = data[list_var_continuous].astype(float)
data.dtypes
intended_balcon_amount float64 prev_address_months_count float64 bank_months_count float64 current_address_months_count float64 session_length_in_minutes float64 device_distinct_emails_8w float64 fraud_bool category foreign_request float64 phone_mobile_valid float64 has_other_cards float64 proposed_credit_limit float64 device_os category source category housing_status category keep_alive_session float64 device_fraud_count float64 phone_home_valid float64 credit_risk_score float64 email_is_free float64 income float64 employment_status category date_of_birth_distinct_emails_4w float64 bank_branch_count_8w float64 velocity_4w float64 velocity_24h float64 velocity_6h float64 zip_count_4w float64 payment_type category days_since_request float64 customer_age float64 name_email_similarity float64 month float64 dtype: object
Separación en train y test estratificado¶
data_status = data['fraud_bool']\
.value_counts(normalize=True)\
.mul(100).rename('percent').reset_index()
data_status_conteo = data['fraud_bool'].value_counts().reset_index()
data_status_pc = pd.merge(data_status,
data_status_conteo, on=['fraud_bool'], how='inner')
fig = px.histogram(data_status_pc, x="fraud_bool", y=['percent'])
fig.show()
from sklearn.model_selection import train_test_split
X_data, X_data_test, y_data, y_data_test = train_test_split(
data.drop('fraud_bool', axis=1),
data['fraud_bool'],
stratify=data['fraud_bool'],
test_size=0.2
)
data_train = pd.concat([X_data, y_data], axis=1)
data_test = pd.concat([X_data_test, y_data_test], axis=1)
print('== Train\n', data_train['fraud_bool'].value_counts(normalize=True))
print('== Test\n', data_test['fraud_bool'].value_counts(normalize=True))
== Train fraud_bool 0 0.988971 1 0.011029 Name: proportion, dtype: float64 == Test fraud_bool 0 0.98897 1 0.01103 Name: proportion, dtype: float64
- Dividimos el conjunto de datos en train y test,dejando a train con un 20 % de los datos
- Asimismo comprobamos que la division de los valores entre Train y Test a resultado equitativa y que representa a todo el dataset
Visualización descriptiva de los datos¶
Veo el número de valores nulos por filas y por columnas, como sustituimos los nulos en el notebook anterior(antes eran todos aquellos menores de 0)
data_series_null_columns = data.isnull().sum().sort_values(ascending=False)
data_series_null_rows = data.isnull().sum(axis=1).sort_values(ascending=False)
print(data_series_null_columns.shape, data_series_null_rows.shape)
data_null_columnas = pd.DataFrame(data_series_null_columns, columns=['nulos_columnas'])
data_null_filas = pd.DataFrame(data_series_null_rows, columns=['nulos_filas'])
data_null_filas['target'] = data['fraud_bool'].copy()
data_null_columnas['porcentaje_columnas'] = data_null_columnas['nulos_columnas']/data.shape[0]
data_null_filas['porcentaje_filas'] = data_null_filas['nulos_filas']/data.shape[1]
(32,) (1000000,)
data_null_columnas
| nulos_columnas | porcentaje_columnas | |
|---|---|---|
| intended_balcon_amount | 742523 | 0.742523 |
| prev_address_months_count | 712920 | 0.712920 |
| bank_months_count | 253635 | 0.253635 |
| current_address_months_count | 4254 | 0.004254 |
| session_length_in_minutes | 2015 | 0.002015 |
| device_distinct_emails_8w | 359 | 0.000359 |
| velocity_6h | 0 | 0.000000 |
| date_of_birth_distinct_emails_4w | 0 | 0.000000 |
| bank_branch_count_8w | 0 | 0.000000 |
| velocity_4w | 0 | 0.000000 |
| velocity_24h | 0 | 0.000000 |
| payment_type | 0 | 0.000000 |
| zip_count_4w | 0 | 0.000000 |
| income | 0 | 0.000000 |
| days_since_request | 0 | 0.000000 |
| customer_age | 0 | 0.000000 |
| name_email_similarity | 0 | 0.000000 |
| employment_status | 0 | 0.000000 |
| phone_home_valid | 0 | 0.000000 |
| email_is_free | 0 | 0.000000 |
| credit_risk_score | 0 | 0.000000 |
| device_fraud_count | 0 | 0.000000 |
| keep_alive_session | 0 | 0.000000 |
| housing_status | 0 | 0.000000 |
| source | 0 | 0.000000 |
| device_os | 0 | 0.000000 |
| proposed_credit_limit | 0 | 0.000000 |
| has_other_cards | 0 | 0.000000 |
| phone_mobile_valid | 0 | 0.000000 |
| foreign_request | 0 | 0.000000 |
| fraud_bool | 0 | 0.000000 |
| month | 0 | 0.000000 |
data_null_filas.head()
| nulos_filas | target | porcentaje_filas | |
|---|---|---|---|
| 415911 | 5 | 0 | 0.15625 |
| 539043 | 5 | 0 | 0.15625 |
| 315076 | 5 | 0 | 0.15625 |
| 980028 | 5 | 0 | 0.15625 |
| 281065 | 5 | 0 | 0.15625 |
columnas_numericas = data_train.select_dtypes(include=[np.number])
variables_numericas = columnas_numericas.columns.tolist()
print(variables_numericas)
['intended_balcon_amount', 'prev_address_months_count', 'bank_months_count', 'current_address_months_count', 'session_length_in_minutes', 'device_distinct_emails_8w', 'foreign_request', 'phone_mobile_valid', 'has_other_cards', 'proposed_credit_limit', 'keep_alive_session', 'device_fraud_count', 'phone_home_valid', 'credit_risk_score', 'email_is_free', 'income', 'date_of_birth_distinct_emails_4w', 'bank_branch_count_8w', 'velocity_4w', 'velocity_24h', 'velocity_6h', 'zip_count_4w', 'days_since_request', 'customer_age', 'name_email_similarity', 'month']
Creamos una lista con las variables numéricas, para agilizar la realización de los gráficos
import warnings
warnings.filterwarnings('ignore')
#for i in list(bf_train.columns):
for i in variables_numericas:
f_aux.plot_feature(data_train, col_name=i, isContinuous=True, target='fraud_bool')
Estos gráficos nos dan informacion detallada sobre cada una de las variables, dividiendolas en numéricas y categóricas, siendo las numéricas las azules. Asimismo comparamos algunas variables con nuestra variable objetivo, como es la "velocity_24" para comparar ambas variables
Tratamiento de las variables continuas¶
A continuación, se tratan los valores missing, las correlaciones de las vairbales continuas y los outlier
variables_numericas
['intended_balcon_amount', 'prev_address_months_count', 'bank_months_count', 'current_address_months_count', 'session_length_in_minutes', 'device_distinct_emails_8w', 'foreign_request', 'phone_mobile_valid', 'has_other_cards', 'proposed_credit_limit', 'keep_alive_session', 'device_fraud_count', 'phone_home_valid', 'credit_risk_score', 'email_is_free', 'income', 'date_of_birth_distinct_emails_4w', 'bank_branch_count_8w', 'velocity_4w', 'velocity_24h', 'velocity_6h', 'zip_count_4w', 'days_since_request', 'customer_age', 'name_email_similarity', 'month']
Tratamiento de outliers¶
f_aux.get_deviation_of_mean_perc(data_train, list_var_continuous, target='fraud_bool', multiplier=3)
| variable | sum_outlier_values | target_value | percentage | |
|---|---|---|---|---|
| 0 | intended_balcon_amount | 1461 | 0 | 0.989733 |
| 1 | prev_address_months_count | 6938 | 0 | 0.993658 |
| 2 | current_address_months_count | 17022 | 0 | 0.984197 |
| 3 | session_length_in_minutes | 18844 | 0 | 0.979728 |
| 4 | device_distinct_emails_8w | 25369 | 0 | 0.962237 |
| 5 | foreign_request | 20250 | 0 | 0.978222 |
| 6 | proposed_credit_limit | 4970 | 0 | 0.867203 |
| 7 | credit_risk_score | 2899 | 0 | 0.962746 |
| 8 | date_of_birth_distinct_emails_4w | 4955 | 0 | 0.993744 |
| 9 | bank_branch_count_8w | 32743 | 0 | 0.989830 |
| 10 | velocity_24h | 446 | 0 | 0.997758 |
| 11 | velocity_6h | 3505 | 0 | 0.993153 |
| 12 | zip_count_4w | 12990 | 0 | 0.990069 |
| 13 | days_since_request | 14177 | 0 | 0.987515 |
| 14 | customer_age | 6300 | 0 | 0.959206 |
Vemos que los valores outlier no son demasiado significativos dentro del dataset, el valor "proposed_credit_limit" es aquel que en teoría tiene mas importancia para detectar el fraude, dado que cuenta con un 12% de valores en fraude, asimismo vemos que el valor "velocity_24h" es aquel que en teoría mas importancia tiene, dado que todos sus valores outlier se encuentran dentro de no fraude.
Con esta tabla podemos ver qué variables son mas comunes, durante un fraude
Correlaciones¶
f_aux.get_corr_matrix(dataset = data_train[list_var_continuous],
metodo='pearson', size_figure=[10,8])
0
Este gráfico intenta ver la relación entre fraud_bool y otras variables numéricas continuas en el conjunto de datos.
- Colores cercanos al verde: correlación positiva.
- Colores Cercanos al morado: correlación negativa.
- Las celdas sin color, nos dicen que no hay una correlación fuerte, ya sea positiva o negativa.
Los valores para fraud_bool parecen ser bajos, dado que la mayoría de las celdas en su fila y columna son oscuras, lo que sugiere que no hay muchas correlaciones fuertes entre fraud_bool y las otras variables.
Hay algunas celdas con un color ligeramente más claro o más oscuro en la fila y columna de fraud_bool, lo que indica algunas correlaciones moderadas.
# con esta funcion podemos sacar que variables se encuentran correlacionadas entre si
corr = data_train[list_var_continuous].corr('pearson')
new_corr = corr.abs()
new_corr.loc[:,:] = np.tril(new_corr, k=-1) # matriz triangular inferior, esto se coge con n.tril
new_corr = new_corr.stack().to_frame('correlation').reset_index().sort_values(by='correlation', ascending=False)
new_corr[new_corr['correlation']>0.6]# sacacmos aquellos valorees con una correlacion mayor a 0.6 osea las mas correlacionadas
| level_0 | level_1 | correlation | |
|---|---|---|---|
| 643 | month | velocity_4w | 0.848311 |
| 335 | credit_risk_score | proposed_credit_limit | 0.605631 |
list_var_continuous
['intended_balcon_amount', 'prev_address_months_count', 'bank_months_count', 'current_address_months_count', 'session_length_in_minutes', 'device_distinct_emails_8w', 'foreign_request', 'phone_mobile_valid', 'has_other_cards', 'proposed_credit_limit', 'keep_alive_session', 'device_fraud_count', 'phone_home_valid', 'credit_risk_score', 'email_is_free', 'income', 'date_of_birth_distinct_emails_4w', 'bank_branch_count_8w', 'velocity_4w', 'velocity_24h', 'velocity_6h', 'zip_count_4w', 'days_since_request', 'customer_age', 'name_email_similarity', 'month']
# con esto sacamos la suma de los valores nulos para la variable.
f_aux.get_percent_null_values_target(data_train, list_var_continuous, target='fraud_bool')
| no_fraud | fraud | variable | sum_null_values | porcentaje_sum_null_values | |
|---|---|---|---|---|---|
| 0 | 0.986899 | 0.013101 | intended_balcon_amount | 593828 | 0.742285 |
| 1 | 0.985784 | 0.014216 | prev_address_months_count | 570267 | 0.712834 |
| 2 | 0.983762 | 0.016238 | bank_months_count | 202677 | 0.253346 |
| 3 | 0.995924 | 0.004076 | current_address_months_count | 3435 | 0.004294 |
| 4 | 0.990752 | 0.009248 | session_length_in_minutes | 1622 | 0.002027 |
| 5 | 0.992806 | 0.007194 | device_distinct_emails_8w | 278 | 0.000347 |
Opción 0:¶
Algunos algoritmos aceptan en su input valores missing
Opción 1:¶
eliminar todas las filas que tengan valores nulos.
Opción 2:¶
Imputar los valores missing por:
- media
- mediana
- maximo
- minimo
- valores extremos https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html
Decido rellenar todas las columnas continuas menos 'fraud_bool' por el valor -99. De esta manera, diferencio los outlier del resto de la muestra poninendo un valor muy separado del resto de la variable. Se puede explorar el resultado del modelo utilizando diferentes métodos
list_vars = list(set(list_var_continuous)-set(['fraud_bool']))
data_train[list_vars] = data_train[list_vars].fillna(-99)
data_test[list_vars] = data_test[list_vars].fillna(-99)
list_vars
['zip_count_4w', 'velocity_6h', 'device_distinct_emails_8w', 'days_since_request', 'velocity_4w', 'has_other_cards', 'session_length_in_minutes', 'phone_mobile_valid', 'date_of_birth_distinct_emails_4w', 'current_address_months_count', 'prev_address_months_count', 'customer_age', 'velocity_24h', 'intended_balcon_amount', 'month', 'proposed_credit_limit', 'email_is_free', 'device_fraud_count', 'bank_branch_count_8w', 'keep_alive_session', 'credit_risk_score', 'bank_months_count', 'phone_home_valid', 'income', 'name_email_similarity', 'foreign_request']
data_test['fraud_bool'].isnull().sum()
0
f_aux.get_percent_null_values_target(data_test, list_var_continuous, target='fraud_bool')
No existen variables con valores nulos
Opción 3:¶
- https://scikit-learn.org/stable/modules/impute.html. Utilizar un modelo de regresión para rellenar los valores mmissing de alguna variable muy importante, por ejemplo: KNN, regresion lineal, xgboost. Pero, cuidado con el sobreajuste.
Vamos a usar KNNImputer para imputar los valores missing de la variable fraus_bool usando como regresoras todas las variables continuas
# aqui no tratamos lo valores outliers
X_train = data_train[list(set(list_var_continuous))]
X_test = data_test[list(set(list_var_continuous))]
imputer = KNNImputer(n_neighbors=2, weights="uniform")
model = imputer.fit(X_train)
pd_input_train = pd.DataFrame(model.transform(X_train),
columns=[i+'_input' for i in list(set(list_var_continuous))],index=data_train.index)
pd_input_test = pd.DataFrame(model.transform(X_test),
columns=[i+'_input' for i in list(set(list_var_continuous))],index=data_test.index)
data_input_train = pd.concat([data_train, pd_input_train],axis=1).drop(list(set(list_var_continuous)),axis=1)
data_input_test = pd.concat([data_test, pd_input_test],axis=1).drop(list(set(list_var_continuous)),axis=1)
data_input_train.shape
(800000, 32)
f_aux.get_percent_null_values_target(data_input_train, [i+'_input' for i in list_var_continuous], target='fraud_bool')
No existen variables con valores nulos
list_var_continuous = list(data_input_train.select_dtypes('float').columns)
f_aux.get_corr_matrix(dataset = data_input_train[list_var_continuous],
metodo='pearson', size_figure=[10,8])
0
Este gráfico de Pearson, mide la relación lineal entre pares de variables. La escala de colores va de -0.8 a 0.6.
- Colores cercanos al verde: correlación positiva.
- Colores Cercanos al morado: correlación negativa.
- Las celdas sin color, nos dicen que no hay una correlación fuerte, ya sea positiva o negativa.
Podemos ver algunos casos de correlación muy negativa de las variables, sobre todo en la parte izquierda del gráfico por lo tanto estas cuantan con fuerte correlación negativa con la variable
data_input_train.columns
Index(['device_os', 'source', 'housing_status', 'employment_status',
'payment_type', 'fraud_bool', 'velocity_6h_input',
'device_distinct_emails_8w_input', 'days_since_request_input',
'velocity_4w_input', 'prev_address_months_count_input',
'intended_balcon_amount_input', 'month_input', 'email_is_free_input',
'bank_branch_count_8w_input', 'keep_alive_session_input',
'credit_risk_score_input', 'bank_months_count_input',
'phone_home_valid_input', 'income_input', 'foreign_request_input',
'zip_count_4w_input', 'has_other_cards_input',
'session_length_in_minutes_input', 'phone_mobile_valid_input',
'date_of_birth_distinct_emails_4w_input',
'current_address_months_count_input', 'customer_age_input',
'velocity_24h_input', 'proposed_credit_limit_input',
'device_fraud_count_input', 'name_email_similarity_input'],
dtype='object')
Tratamiento de las variables categoricas¶
Para la correlacion de spearman es necesario convertir las variables categoricas en numericas y luego obtener la correlación
También está el coeficiente V-Cramer https://stackoverflow.com/questions/46498455/categorical-features-correlation
list_var_cat
['fraud_bool', 'device_os', 'source', 'housing_status', 'employment_status', 'payment_type']
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["employment_status"])
print(confusion_matrix)
f_aux.cramers_v(confusion_matrix.values)
employment_status CA CB CC CD CE CF CG payment_type AA 149932 33588 5719 4133 7062 6324 133 AB 215312 38674 13929 7580 5542 15182 116 AC 149864 21368 8063 7636 3646 10730 78 AD 68941 16899 2435 1883 1942 3011 42 AE 173 31 6 8 6 11 1
0.05673728868605832
Con la variabe crosstab podemos ver la correlacion de pearson de las variables, en este caso de payment_type y de employment_status, dado que son unsa de las mas importantes
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["payment_type"])
f_aux.cramers_v(confusion_matrix.values)
0.9999999999999999
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["housing_status"])
f_aux.cramers_v(confusion_matrix.values)
0.09821978810919921
Tratamiento de valores nulos¶
En las variables categoricas, los valores nulos se suelen sustituir por una nueva clase: "sin valor" o por la moda
Como ya hemos mencionado, en el notebook anterior, en nuestro dataset, los valores -1 son los equivalentes a los nulos
data_input_train[list_var_cat] = data_input_train[list_var_cat].astype("object").fillna("SIN VALOR").astype("category")
data_input_test[list_var_cat] = data_input_test[list_var_cat].astype("object").fillna("SIN VALOR").astype("category")
guardamos lo valores¶
data_input_train.to_csv("../Practica_Eda_Miguel_Garcia/data/train_data_preprocessing_missing_outlier.csv")
data_input_test.to_csv("../Practica_Eda_Miguel_Garcia/data/test_data_preprocessing_missing_outlier.csv")
print(data_input_train.shape, data_input_test.shape)
data_input_test.dtypes
(800000, 32) (200000, 32)
device_os category source category housing_status category employment_status category payment_type category fraud_bool category velocity_6h_input float64 device_distinct_emails_8w_input float64 days_since_request_input float64 velocity_4w_input float64 prev_address_months_count_input float64 intended_balcon_amount_input float64 month_input float64 email_is_free_input float64 bank_branch_count_8w_input float64 keep_alive_session_input float64 credit_risk_score_input float64 bank_months_count_input float64 phone_home_valid_input float64 income_input float64 foreign_request_input float64 zip_count_4w_input float64 has_other_cards_input float64 session_length_in_minutes_input float64 phone_mobile_valid_input float64 date_of_birth_distinct_emails_4w_input float64 current_address_months_count_input float64 customer_age_input float64 velocity_24h_input float64 proposed_credit_limit_input float64 device_fraud_count_input float64 name_email_similarity_input float64 dtype: object
Conclusión¶
Primeramente separamos en test y train, con un 20% para test,acto seguido, preparamos los datos para su visualización de las variables usando una funcion de func-aux, estos gráficos nos dan informacion detallada de cada una de las variables que encontramos actualmente en el dataset.
A continuación tratamos los outliers y vemos las correlaciones existentes entre las diferentes variables del dataset. Nos dan varias opciones para realizar el tratamiento de los valores nulos, nosotros elegimos la Opción tres, dadoq eu es la mas recomendable(Utilizar un modelo de regresión para rellenar los valores mmissing de alguna variable muy importante).
Finalmente vemos la relación existente con las variables categóricas, donde podemos ver como se relacionan entre ellas mismas.